package com.fub.update;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.widget.Toast;
import com.fub.guidance.BuildingDAO;
import com.fub.guidance.BuildingVersionDAO;
import com.fub.guidance.RoomDataDAO;
import com.fub.guidance.RoutingGraphDAO;
import com.fub.update.BuildingUpdate.UpdateType;
import com.fub.util.PreferenceDAO;

public class UpdateService extends Service {
	private ServerStreamParser netManager;
	private UpdateServiceBinder binder = new UpdateServiceBinder();
	private RoutingGraphDAO nodeEdgeDBService;
	private BuildingDAO wallDbService;
	private RoomDataDAO updateDBService;
	private Dialog prozessDialog;
	private BuildingVersionDAO buildingVersionDBService;
	private String buildingId;
	private float latitude;
	private float longitude;
	private int buildingVersion;
	private Context context;
	private Handler handler;
	private PreferenceDAO preferenceDAO;

	@Override
	public IBinder onBind(Intent arg0) {
		return this.binder;
	}

	public class UpdateServiceBinder extends Binder {
		public UpdateService getService() {
			return UpdateService.this;
		}
	}

	@Override
	public void onCreate() {
		super.onCreate();
		this.netManager = new ServerStreamParser(getApplicationContext());
		this.buildingVersionDBService = new BuildingVersionDAO(
				getApplicationContext());
		this.nodeEdgeDBService = new RoutingGraphDAO(getApplicationContext());
		this.wallDbService = new BuildingDAO(getApplicationContext());
		this.updateDBService = new RoomDataDAO(getApplicationContext());
		this.preferenceDAO = new PreferenceDAO(getApplicationContext());
		this.handler = new Handler();
	}

	/**
	 * Checks if new buildings or updates are available.
	 * 
	 * @throws IOException
	 */
	public List<BuildingUpdate> getUpdates() throws IOException {
		List<BuildingUpdate> updates = new ArrayList<BuildingUpdate>();
		BuildingVersionMap buildingMapServer = this.netManager
				.parseFromServerAndGetBuildingVersions();
		BuildingVersionMap buildingMapLocal = this.buildingVersionDBService
				.getAllBuildingVersionsMap();
		updates.addAll(compareLocalWithServerBuildings(buildingMapLocal,
				buildingMapServer));
		updates.addAll(checkForNewUpdates(buildingMapLocal, buildingMapServer));
		return updates;
	}

	/**
	 * Updates an existing or creates a new building.
	 * 
	 * @param update
	 *            the update, which contains the building id and the update type
	 * @param context
	 *            the application context
	 */
	public void updateOrAddNewBuilding(BuildingUpdate update, Context context) {
		this.context = context;
		if (update.getType() == BuildingUpdate.UpdateType.BUILDING_NEW) {
			addNewBuilding(update);
		} else {
			updateBuilding(update);
		}
	}

	private List<BuildingUpdate> compareLocalWithServerBuildings(
			BuildingVersionMap buildingMapLocal,
			BuildingVersionMap buildingMapServer) {
		List<BuildingUpdate> updates = new ArrayList<BuildingUpdate>();
		for (String buildingId : buildingMapServer.getBuildingsIds()) {
			if (buildingMapLocal.getVersion(buildingId) == -1) {
				BuildingUpdate update = new BuildingUpdate(buildingId,
						buildingMapServer.getVersion(buildingId),
						UpdateType.BUILDING_NEW,
						buildingMapServer.getLatitude(buildingId),
						buildingMapServer.getLongitude(buildingId));
				updates.add(update);
			}
		}
		return updates;
	}

	private List<BuildingUpdate> checkForNewUpdates(
			BuildingVersionMap versionMapFromLocal,
			BuildingVersionMap versionMapFromServer) {
		List<BuildingUpdate> updates = new ArrayList<BuildingUpdate>();
		for (String buildingId : versionMapFromLocal.getBuildingsIds()) {
			if (versionMapFromServer.contains(buildingId)) {
				int versionLocal = versionMapFromLocal.getVersion(buildingId);
				int versionServer = versionMapFromServer.getVersion(buildingId);

				if (versionServer > versionLocal) {
					BuildingUpdate update = new BuildingUpdate(buildingId,
							versionServer, UpdateType.BUILDING_UPDATE,
							versionMapFromServer.getLatitude(buildingId),
							versionMapFromServer.getLongitude(buildingId));
					updates.add(update);
				}
			}
		}
		return updates;
	}

	private void addNewBuilding(BuildingUpdate update) {
		this.buildingId = update.getBuildingId();
		this.latitude = update.getLatitude();
		this.longitude = update.getLongitude();
		this.buildingVersion = update.getVersion();
		showProcessDialog(getResources().getString(
				com.fub.R.string.importRunning));
		Thread thread = new Thread() {
			@Override
			public void run() {
				// insert building data into db
				UpdateService.this.netManager
						.parseNodesEdgesWallsRelations(UpdateService.this.netManager
								.getGraphStream(UpdateService.this.buildingId));
				UpdateService.this.netManager
						.parseUpdates(UpdateService.this.netManager
								.getUpdateServerStream(UpdateService.this.buildingId));
				// add building to version control
				addBuildingToVersionTable(UpdateService.this.buildingId,
						UpdateService.this.latitude,
						UpdateService.this.longitude);
				UpdateService.this.buildingVersionDBService
						.updateBuildingVersion(UpdateService.this.buildingId,
								UpdateService.this.buildingVersion);
				// set current building
				UpdateService.this.preferenceDAO
						.setBuildingId(UpdateService.this.buildingId);
				// close the update activity
				UpdateService.this.handler
						.post(UpdateService.this.hideProcessDialogAndShowToast);
			}
		};
		thread.start();
	}

	private Runnable hideProcessDialogAndShowToast = new Runnable() {
		@Override
		public void run() {
			UpdateService.this.prozessDialog.hide();
			int numberNodes = UpdateService.this.nodeEdgeDBService
					.getAllNodes().size();
			int numberEdges = UpdateService.this.nodeEdgeDBService
					.getAllEdges().size();
			int numberWalls = UpdateService.this.wallDbService.getAllWalls()
					.size();
			Toast toast = Toast.makeText(getApplicationContext(), String
					.format(getResources().getString(
							com.fub.R.string.importSuccess),
							UpdateService.this.buildingId, numberNodes,
							numberEdges, numberWalls), Toast.LENGTH_LONG);
			toast.show();
			try {
				((UpdateActivity) (UpdateService.this.context)).finish();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};

	private void showProcessDialog(String text) {
		this.prozessDialog = ProgressDialog.show(this.context, "", text, true);
		this.prozessDialog.show();
	}

	private void updateBuilding(BuildingUpdate update) {
		this.buildingId = update.getBuildingId();
		this.buildingVersion = update.getVersion();
		showProcessDialog(getResources().getString(
				com.fub.R.string.importRunning));

		this.updateDBService.deleteRoomData(this.buildingId);
		Thread thread = new Thread() {
			@Override
			public void run() {
				UpdateService.this.netManager
						.parseUpdates(UpdateService.this.netManager
								.getUpdateServerStream(UpdateService.this.buildingId));
				// add building to version control
				UpdateService.this.buildingVersionDBService
						.updateBuildingVersion(UpdateService.this.buildingId,
								UpdateService.this.buildingVersion);
				UpdateService.this.handler
						.post(UpdateService.this.hideProcessDialogAndShowToast);
			}
		};
		thread.start();
	}

	private void addBuildingToVersionTable(String buildingId, float latitude,
			float longitude) {
		if (this.buildingVersionDBService
				.isBuildingInVersionControll(buildingId)) {
			this.buildingVersionDBService
					.resetBuildingInVersionControll(buildingId);
		} else {
			this.buildingVersionDBService.insertBuildingToVersionTable(
					buildingId, latitude, longitude);
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
	}
}
